//
// detail/impl/signal_set_service.ipp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef ASIO_DETAIL_IMPL_SIGNAL_SET_SERVICE_IPP
#define ASIO_DETAIL_IMPL_SIGNAL_SET_SERVICE_IPP

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200)

#include "asio/detail/config.hpp"

#include <cstring>
#include <stdexcept>
#include "asio/detail/signal_blocker.hpp"
#include "asio/detail/signal_set_service.hpp"
#include "asio/detail/static_mutex.hpp"
#include "asio/detail/throw_exception.hpp"

#if defined(ASIO_HAS_IO_URING_AS_DEFAULT)
# include "asio/detail/io_uring_service.hpp"
#else // defined(ASIO_HAS_IO_URING_AS_DEFAULT)

# include "asio/detail/reactor.hpp"

#endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT)

#include "asio/detail/push_options.hpp"

namespace asio {
    namespace detail {

        struct signal_state {
            // Mutex used for protecting global state.
            static_mutex mutex_;

            // The read end of the pipe used for signal notifications.
            int read_descriptor_;

            // The write end of the pipe used for signal notifications.
            int write_descriptor_;

            // Whether the signal state has been prepared for a fork.
            bool fork_prepared_;

            // The head of a linked list of all signal_set_service instances.
            class signal_set_service *service_list_;

            // A count of the number of objects that are registered for each signal.
            std::size_t registration_count_[max_signal_number];
        };

        signal_state *get_signal_state() {
            static signal_state state = {
                    ASIO_STATIC_MUTEX_INIT, -1, -1, false, 0, {0}};
            return &state;
        }

        void asio_signal_handler(int signal_number) {
#if defined(ASIO_WINDOWS) \
 || defined(ASIO_WINDOWS_RUNTIME) \
 || defined(__CYGWIN__)
            signal_set_service::deliver_signal(signal_number);
#else // defined(ASIO_WINDOWS)
            //   || defined(ASIO_WINDOWS_RUNTIME)
            //   || defined(__CYGWIN__)
            int saved_errno = errno;
            signal_state *state = get_signal_state();
            signed_size_type result = ::write(state->write_descriptor_,
                                              &signal_number, sizeof(signal_number));
            (void) result;
            errno = saved_errno;
#endif // defined(ASIO_WINDOWS)
            //   || defined(ASIO_WINDOWS_RUNTIME)
            //   || defined(__CYGWIN__)

#if defined(ASIO_HAS_SIGNAL) && !defined(ASIO_HAS_SIGACTION)
            ::signal(signal_number, asio_signal_handler);
#endif // defined(ASIO_HAS_SIGNAL) && !defined(ASIO_HAS_SIGACTION)
        }

#if !defined(ASIO_WINDOWS) \
 && !defined(ASIO_WINDOWS_RUNTIME) \
 && !defined(__CYGWIN__)

        class signal_set_service::pipe_read_op :
# if defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                public io_uring_operation
# else // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                public reactor_op
# endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
        {
        public:
# if defined(ASIO_HAS_IO_URING_AS_DEFAULT)
            pipe_read_op()
              : io_uring_operation(asio::error_code(), &pipe_read_op::do_prepare,
                  &pipe_read_op::do_perform, pipe_read_op::do_complete)
            {
            }

            static void do_prepare(io_uring_operation*, ::io_uring_sqe* sqe)
            {
              signal_state* state = get_signal_state();

              int fd = state->read_descriptor_;
              ::io_uring_prep_poll_add(sqe, fd, POLLIN);
            }

            static bool do_perform(io_uring_operation*, bool)
            {
              signal_state* state = get_signal_state();

              int fd = state->read_descriptor_;
              int signal_number = 0;
              while (::read(fd, &signal_number, sizeof(int)) == sizeof(int))
                if (signal_number >= 0 && signal_number < max_signal_number)
                  signal_set_service::deliver_signal(signal_number);

              return false;
            }
# else // defined(ASIO_HAS_IO_URING_AS_DEFAULT)

            pipe_read_op()
                    : reactor_op(asio::error_code(),
                                 &pipe_read_op::do_perform, pipe_read_op::do_complete) {
            }

            static status do_perform(reactor_op *) {
                signal_state *state = get_signal_state();

                int fd = state->read_descriptor_;
                int signal_number = 0;
                while (::read(fd, &signal_number, sizeof(int)) == sizeof(int))
                    if (signal_number >= 0 && signal_number < max_signal_number)
                        signal_set_service::deliver_signal(signal_number);

                return not_done;
            }

# endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT)

            static void do_complete(void * /*owner*/, operation *base,
                                    const asio::error_code & /*ec*/,
                                    std::size_t /*bytes_transferred*/) {
                pipe_read_op *o(static_cast<pipe_read_op *>(base));
                delete o;
            }
        };

#endif // !defined(ASIO_WINDOWS)
        //   && !defined(ASIO_WINDOWS_RUNTIME)
        //   && !defined(__CYGWIN__)

        signal_set_service::signal_set_service(execution_context &context)
                : execution_context_service_base<signal_set_service>(context),
                  scheduler_(asio::use_service<scheduler_impl>(context)),
#if !defined(ASIO_WINDOWS) \
 && !defined(ASIO_WINDOWS_RUNTIME) \
 && !defined(__CYGWIN__)
# if defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                io_uring_service_(asio::use_service<io_uring_service>(context)),
# else // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                  reactor_(asio::use_service<reactor>(context)),
# endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
#endif // !defined(ASIO_WINDOWS)
                //   && !defined(ASIO_WINDOWS_RUNTIME)
                //   && !defined(__CYGWIN__)
                  next_(0),
                  prev_(0) {
            get_signal_state()->mutex_.init();

#if !defined(ASIO_WINDOWS) \
 && !defined(ASIO_WINDOWS_RUNTIME) \
 && !defined(__CYGWIN__)
# if defined(ASIO_HAS_IO_URING_AS_DEFAULT)
            io_uring_service_.init_task();
# else // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
            reactor_.init_task();
# endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
#endif // !defined(ASIO_WINDOWS)
            //   && !defined(ASIO_WINDOWS_RUNTIME)
            //   && !defined(__CYGWIN__)

            for (int i = 0; i < max_signal_number; ++i)
                registrations_[i] = 0;

            add_service(this);
        }

        signal_set_service::~signal_set_service() {
            remove_service(this);
        }

        void signal_set_service::shutdown() {
            remove_service(this);

            op_queue <operation> ops;

            for (int i = 0; i < max_signal_number; ++i) {
                registration *reg = registrations_[i];
                while (reg) {
                    ops.push(*reg->queue_);
                    reg = reg->next_in_table_;
                }
            }

            scheduler_.abandon_operations(ops);
        }

        void signal_set_service::notify_fork(execution_context::fork_event fork_ev) {
#if !defined(ASIO_WINDOWS) \
 && !defined(ASIO_WINDOWS_RUNTIME) \
 && !defined(__CYGWIN__)
            signal_state *state = get_signal_state();
            static_mutex::scoped_lock lock(state->mutex_);

            switch (fork_ev) {
                case execution_context::fork_prepare: {
                    int read_descriptor = state->read_descriptor_;
                    state->fork_prepared_ = true;
                    lock.unlock();
# if defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                    (void)read_descriptor;
                    io_uring_service_.deregister_io_object(io_object_data_);
                    io_uring_service_.cleanup_io_object(io_object_data_);
# else // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                    reactor_.deregister_internal_descriptor(read_descriptor, reactor_data_);
                    reactor_.cleanup_descriptor_data(reactor_data_);
# endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                }
                    break;
                case execution_context::fork_parent:
                    if (state->fork_prepared_) {
                        int read_descriptor = state->read_descriptor_;
                        state->fork_prepared_ = false;
                        lock.unlock();
# if defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                        (void)read_descriptor;
                        io_uring_service_.register_internal_io_object(io_object_data_,
                            io_uring_service::read_op, new pipe_read_op);
# else // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                        reactor_.register_internal_descriptor(reactor::read_op,
                                                              read_descriptor, reactor_data_, new pipe_read_op);
# endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                    }
                    break;
                case execution_context::fork_child:
                    if (state->fork_prepared_) {
                        asio::detail::signal_blocker blocker;
                        close_descriptors();
                        open_descriptors();
                        int read_descriptor = state->read_descriptor_;
                        state->fork_prepared_ = false;
                        lock.unlock();
# if defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                        (void)read_descriptor;
                        io_uring_service_.register_internal_io_object(io_object_data_,
                            io_uring_service::read_op, new pipe_read_op);
# else // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                        reactor_.register_internal_descriptor(reactor::read_op,
                                                              read_descriptor, reactor_data_, new pipe_read_op);
# endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                    }
                    break;
                default:
                    break;
            }
#else // !defined(ASIO_WINDOWS)
            //   && !defined(ASIO_WINDOWS_RUNTIME)
            //   && !defined(__CYGWIN__)
        (void)fork_ev;
#endif // !defined(ASIO_WINDOWS)
            //   && !defined(ASIO_WINDOWS_RUNTIME)
            //   && !defined(__CYGWIN__)
        }

        void signal_set_service::construct(
                signal_set_service::implementation_type &impl) {
            impl.signals_ = 0;
        }

        void signal_set_service::destroy(
                signal_set_service::implementation_type &impl) {
            asio::error_code ignored_ec;
            clear(impl, ignored_ec);
            cancel(impl, ignored_ec);
        }

        asio::error_code signal_set_service::add(
                signal_set_service::implementation_type &impl,
                int signal_number, asio::error_code &ec) {
            // Check that the signal number is valid.
            if (signal_number < 0 || signal_number >= max_signal_number) {
                ec = asio::error::invalid_argument;
                return ec;
            }

            signal_state *state = get_signal_state();
            static_mutex::scoped_lock lock(state->mutex_);

            // Find the appropriate place to insert the registration.
            registration **insertion_point = &impl.signals_;
            registration *next = impl.signals_;
            while (next && next->signal_number_ < signal_number) {
                insertion_point = &next->next_in_set_;
                next = next->next_in_set_;
            }

            // Only do something if the signal is not already registered.
            if (next == 0 || next->signal_number_ != signal_number) {
                registration *new_registration = new registration;

#if defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION)
                // Register for the signal if we're the first.
                if (state->registration_count_[signal_number] == 0)
                {
# if defined(ASIO_HAS_SIGACTION)
                  using namespace std; // For memset.
                  struct sigaction sa;
                  memset(&sa, 0, sizeof(sa));
                  sa.sa_handler = asio_signal_handler;
                  sigfillset(&sa.sa_mask);
                  if (::sigaction(signal_number, &sa, 0) == -1)
# else // defined(ASIO_HAS_SIGACTION)
                  if (::signal(signal_number, asio_signal_handler) == SIG_ERR)
# endif // defined(ASIO_HAS_SIGACTION)
                  {
# if defined(ASIO_WINDOWS) || defined(__CYGWIN__)
                    ec = asio::error::invalid_argument;
# else // defined(ASIO_WINDOWS) || defined(__CYGWIN__)
                    ec = asio::error_code(errno,
                        asio::error::get_system_category());
# endif // defined(ASIO_WINDOWS) || defined(__CYGWIN__)
                    delete new_registration;
                    return ec;
                  }
                }
#endif // defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION)

                // Record the new registration in the set.
                new_registration->signal_number_ = signal_number;
                new_registration->queue_ = &impl.queue_;
                new_registration->next_in_set_ = next;
                *insertion_point = new_registration;

                // Insert registration into the registration table.
                new_registration->next_in_table_ = registrations_[signal_number];
                if (registrations_[signal_number])
                    registrations_[signal_number]->prev_in_table_ = new_registration;
                registrations_[signal_number] = new_registration;

                ++state->registration_count_[signal_number];
            }

            ec = asio::error_code();
            return ec;
        }

        asio::error_code signal_set_service::remove(
                signal_set_service::implementation_type &impl,
                int signal_number, asio::error_code &ec) {
            // Check that the signal number is valid.
            if (signal_number < 0 || signal_number >= max_signal_number) {
                ec = asio::error::invalid_argument;
                return ec;
            }

            signal_state *state = get_signal_state();
            static_mutex::scoped_lock lock(state->mutex_);

            // Find the signal number in the list of registrations.
            registration **deletion_point = &impl.signals_;
            registration *reg = impl.signals_;
            while (reg && reg->signal_number_ < signal_number) {
                deletion_point = &reg->next_in_set_;
                reg = reg->next_in_set_;
            }

            if (reg != 0 && reg->signal_number_ == signal_number) {
#if defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION)
                // Set signal handler back to the default if we're the last.
                if (state->registration_count_[signal_number] == 1)
                {
# if defined(ASIO_HAS_SIGACTION)
                  using namespace std; // For memset.
                  struct sigaction sa;
                  memset(&sa, 0, sizeof(sa));
                  sa.sa_handler = SIG_DFL;
                  if (::sigaction(signal_number, &sa, 0) == -1)
# else // defined(ASIO_HAS_SIGACTION)
                  if (::signal(signal_number, SIG_DFL) == SIG_ERR)
# endif // defined(ASIO_HAS_SIGACTION)
                  {
# if defined(ASIO_WINDOWS) || defined(__CYGWIN__)
                    ec = asio::error::invalid_argument;
# else // defined(ASIO_WINDOWS) || defined(__CYGWIN__)
                    ec = asio::error_code(errno,
                        asio::error::get_system_category());
# endif // defined(ASIO_WINDOWS) || defined(__CYGWIN__)
                    return ec;
                  }
                }
#endif // defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION)

                // Remove the registration from the set.
                *deletion_point = reg->next_in_set_;

                // Remove the registration from the registration table.
                if (registrations_[signal_number] == reg)
                    registrations_[signal_number] = reg->next_in_table_;
                if (reg->prev_in_table_)
                    reg->prev_in_table_->next_in_table_ = reg->next_in_table_;
                if (reg->next_in_table_)
                    reg->next_in_table_->prev_in_table_ = reg->prev_in_table_;

                --state->registration_count_[signal_number];

                delete reg;
            }

            ec = asio::error_code();
            return ec;
        }

        asio::error_code signal_set_service::clear(
                signal_set_service::implementation_type &impl,
                asio::error_code &ec) {
            signal_state *state = get_signal_state();
            static_mutex::scoped_lock lock(state->mutex_);

            while (registration * reg = impl.signals_) {
#if defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION)
                // Set signal handler back to the default if we're the last.
                if (state->registration_count_[reg->signal_number_] == 1)
                {
# if defined(ASIO_HAS_SIGACTION)
                  using namespace std; // For memset.
                  struct sigaction sa;
                  memset(&sa, 0, sizeof(sa));
                  sa.sa_handler = SIG_DFL;
                  if (::sigaction(reg->signal_number_, &sa, 0) == -1)
# else // defined(ASIO_HAS_SIGACTION)
                  if (::signal(reg->signal_number_, SIG_DFL) == SIG_ERR)
# endif // defined(ASIO_HAS_SIGACTION)
                  {
# if defined(ASIO_WINDOWS) || defined(__CYGWIN__)
                    ec = asio::error::invalid_argument;
# else // defined(ASIO_WINDOWS) || defined(__CYGWIN__)
                    ec = asio::error_code(errno,
                        asio::error::get_system_category());
# endif // defined(ASIO_WINDOWS) || defined(__CYGWIN__)
                    return ec;
                  }
                }
#endif // defined(ASIO_HAS_SIGNAL) || defined(ASIO_HAS_SIGACTION)

                // Remove the registration from the registration table.
                if (registrations_[reg->signal_number_] == reg)
                    registrations_[reg->signal_number_] = reg->next_in_table_;
                if (reg->prev_in_table_)
                    reg->prev_in_table_->next_in_table_ = reg->next_in_table_;
                if (reg->next_in_table_)
                    reg->next_in_table_->prev_in_table_ = reg->prev_in_table_;

                --state->registration_count_[reg->signal_number_];

                impl.signals_ = reg->next_in_set_;
                delete reg;
            }

            ec = asio::error_code();
            return ec;
        }

        asio::error_code signal_set_service::cancel(
                signal_set_service::implementation_type &impl,
                asio::error_code &ec) {
            ASIO_HANDLER_OPERATION((scheduler_.context(),
                    "signal_set", &impl, 0, "cancel"));

            op_queue <operation> ops;
            {
                signal_state *state = get_signal_state();
                static_mutex::scoped_lock lock(state->mutex_);

                while (signal_op * op = impl.queue_.front()) {
                    op->ec_ = asio::error::operation_aborted;
                    impl.queue_.pop();
                    ops.push(op);
                }
            }

            scheduler_.post_deferred_completions(ops);

            ec = asio::error_code();
            return ec;
        }

        void signal_set_service::cancel_ops_by_key(
                signal_set_service::implementation_type &impl, void *cancellation_key) {
            op_queue <operation> ops;
            {
                op_queue <signal_op> other_ops;
                signal_state *state = get_signal_state();
                static_mutex::scoped_lock lock(state->mutex_);

                while (signal_op * op = impl.queue_.front()) {
                    impl.queue_.pop();
                    if (op->cancellation_key_ == cancellation_key) {
                        op->ec_ = asio::error::operation_aborted;
                        ops.push(op);
                    } else
                        other_ops.push(op);
                }

                impl.queue_.push(other_ops);
            }

            scheduler_.post_deferred_completions(ops);
        }

        void signal_set_service::deliver_signal(int signal_number) {
            signal_state *state = get_signal_state();
            static_mutex::scoped_lock lock(state->mutex_);

            signal_set_service *service = state->service_list_;
            while (service) {
                op_queue <operation> ops;

                registration *reg = service->registrations_[signal_number];
                while (reg) {
                    if (reg->queue_->empty()) {
                        ++reg->undelivered_;
                    } else {
                        while (signal_op * op = reg->queue_->front()) {
                            op->signal_number_ = signal_number;
                            reg->queue_->pop();
                            ops.push(op);
                        }
                    }

                    reg = reg->next_in_table_;
                }

                service->scheduler_.post_deferred_completions(ops);

                service = service->next_;
            }
        }

        void signal_set_service::add_service(signal_set_service *service) {
            signal_state *state = get_signal_state();
            static_mutex::scoped_lock lock(state->mutex_);

#if !defined(ASIO_WINDOWS) && !defined(__CYGWIN__)
            // If this is the first service to be created, open a new pipe.
            if (state->service_list_ == 0)
                open_descriptors();
#endif // !defined(ASIO_WINDOWS) && !defined(__CYGWIN__)

            // If a scheduler_ object is thread-unsafe then it must be the only
            // scheduler used to create signal_set objects.
            if (state->service_list_ != 0) {
                if (!ASIO_CONCURRENCY_HINT_IS_LOCKING(SCHEDULER,
                                                      service->scheduler_.concurrency_hint())
                    || !ASIO_CONCURRENCY_HINT_IS_LOCKING(SCHEDULER,
                                                         state->service_list_->scheduler_.concurrency_hint())) {
                    std::logic_error ex(
                            "Thread-unsafe execution context objects require "
                            "exclusive access to signal handling.");
                    asio::detail::throw_exception(ex);
                }
            }

            // Insert service into linked list of all services.
            service->next_ = state->service_list_;
            service->prev_ = 0;
            if (state->service_list_)
                state->service_list_->prev_ = service;
            state->service_list_ = service;

#if !defined(ASIO_WINDOWS) \
 && !defined(ASIO_WINDOWS_RUNTIME) \
 && !defined(__CYGWIN__)
            // Register for pipe readiness notifications.
            int read_descriptor = state->read_descriptor_;
            lock.unlock();
# if defined(ASIO_HAS_IO_URING_AS_DEFAULT)
            (void)read_descriptor;
            service->io_uring_service_.register_internal_io_object(
                service->io_object_data_, io_uring_service::read_op, new pipe_read_op);
# else // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
            service->reactor_.register_internal_descriptor(reactor::read_op,
                                                           read_descriptor, service->reactor_data_, new pipe_read_op);
# endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
#endif // !defined(ASIO_WINDOWS)
            //   && !defined(ASIO_WINDOWS_RUNTIME)
            //   && !defined(__CYGWIN__)
        }

        void signal_set_service::remove_service(signal_set_service *service) {
            signal_state *state = get_signal_state();
            static_mutex::scoped_lock lock(state->mutex_);

            if (service->next_ || service->prev_ || state->service_list_ == service) {
#if !defined(ASIO_WINDOWS) \
 && !defined(ASIO_WINDOWS_RUNTIME) \
 && !defined(__CYGWIN__)
                // Disable the pipe readiness notifications.
                int read_descriptor = state->read_descriptor_;
                lock.unlock();
# if defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                (void)read_descriptor;
                service->io_uring_service_.deregister_io_object(service->io_object_data_);
                service->io_uring_service_.cleanup_io_object(service->io_object_data_);
                lock.lock();
# else // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
                service->reactor_.deregister_internal_descriptor(
                        read_descriptor, service->reactor_data_);
                service->reactor_.cleanup_descriptor_data(service->reactor_data_);
                lock.lock();
# endif // defined(ASIO_HAS_IO_URING_AS_DEFAULT)
#endif // !defined(ASIO_WINDOWS)
                //   && !defined(ASIO_WINDOWS_RUNTIME)
                //   && !defined(__CYGWIN__)

                // Remove service from linked list of all services.
                if (state->service_list_ == service)
                    state->service_list_ = service->next_;
                if (service->prev_)
                    service->prev_->next_ = service->next_;
                if (service->next_)
                    service->next_->prev_ = service->prev_;
                service->next_ = 0;
                service->prev_ = 0;

#if !defined(ASIO_WINDOWS) && !defined(__CYGWIN__)
                // If this is the last service to be removed, close the pipe.
                if (state->service_list_ == 0)
                    close_descriptors();
#endif // !defined(ASIO_WINDOWS) && !defined(__CYGWIN__)
            }
        }

        void signal_set_service::open_descriptors() {
#if !defined(ASIO_WINDOWS) \
 && !defined(ASIO_WINDOWS_RUNTIME) \
 && !defined(__CYGWIN__)
            signal_state *state = get_signal_state();

            int pipe_fds[2];
            if (::pipe(pipe_fds) == 0) {
                state->read_descriptor_ = pipe_fds[0];
                ::fcntl(state->read_descriptor_, F_SETFL, O_NONBLOCK);

                state->write_descriptor_ = pipe_fds[1];
                ::fcntl(state->write_descriptor_, F_SETFL, O_NONBLOCK);

#if defined(FD_CLOEXEC)
                ::fcntl(state->read_descriptor_, F_SETFD, FD_CLOEXEC);
                ::fcntl(state->write_descriptor_, F_SETFD, FD_CLOEXEC);
#endif // defined(FD_CLOEXEC)
            } else {
                asio::error_code ec(errno,
                                    asio::error::get_system_category());
                asio::detail::throw_error(ec, "signal_set_service pipe");
            }
#endif // !defined(ASIO_WINDOWS)
            //   && !defined(ASIO_WINDOWS_RUNTIME)
            //   && !defined(__CYGWIN__)
        }

        void signal_set_service::close_descriptors() {
#if !defined(ASIO_WINDOWS) \
 && !defined(ASIO_WINDOWS_RUNTIME) \
 && !defined(__CYGWIN__)
            signal_state *state = get_signal_state();

            if (state->read_descriptor_ != -1)
                ::close(state->read_descriptor_);
            state->read_descriptor_ = -1;

            if (state->write_descriptor_ != -1)
                ::close(state->write_descriptor_);
            state->write_descriptor_ = -1;
#endif // !defined(ASIO_WINDOWS)
            //   && !defined(ASIO_WINDOWS_RUNTIME)
            //   && !defined(__CYGWIN__)
        }

        void signal_set_service::start_wait_op(
                signal_set_service::implementation_type &impl, signal_op *op) {
            scheduler_.work_started();

            signal_state *state = get_signal_state();
            static_mutex::scoped_lock lock(state->mutex_);

            registration *reg = impl.signals_;
            while (reg) {
                if (reg->undelivered_ > 0) {
                    --reg->undelivered_;
                    op->signal_number_ = reg->signal_number_;
                    scheduler_.post_deferred_completion(op);
                    return;
                }

                reg = reg->next_in_set_;
            }

            impl.queue_.push(op);
        }

    } // namespace detail
} // namespace asio

#include "asio/detail/pop_options.hpp"

#endif // ASIO_DETAIL_IMPL_SIGNAL_SET_SERVICE_IPP
